using System;
using System.Collections.Generic;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using System.Globalization;

namespace TfsCodeReviewPolicy
{
    [Serializable]
    public class CodeReviewPolicy : PolicyBase
    {
        #region Constants

        private const string POLICY_DESCRIPTION = "Enforces that any checked-in code must be associated with at least " + 
            "one Code Review work item which has been approved, with no Code Review work items that have been rejected, " + 
            "and that all associated bugs were closed.";
        private const string POLICY_TYPE = "TFS Code Review Policy";
        //private const string INSTALLATION_INSTRUCTIONS = "\n\n" +
        //                " 1. Locate the latest build on TfsBuddyBuildPolicy.dll and place it locally on client. \n" +
        //               @" 2. For VS2005: Add location to HKLM\SOFTWARE\Microsoft\VisualStudio\8.0\TeamFoundation\SourceControl\Checkin Policies\" + "\n" +
        //               @" NOTE: On 64bit machines, the path is: HKLM\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\8.0\TeamFoundation\SourceControl\Checkin Policies" + "\n" +
        //               @" If installing on VS2008, substitute 8.0 with 9.0 in the registry keys above.";
        private const string INSTALLATION_INSTRUCTIONS = "\n\n" +
                        "To install the Code Review TFS Policy: \n" +
                        "Go to: http://www.tfsbugger.com/dev/downloads/" + "\n" +
                        "And download the appropriate policy for your Visual Studio version and operating system flavor (32-bit or 64-bit)." + "\n\n" +
                        "For any other issues, go to: http://www.tfsbugger.com/dev/faq/faq.htm.";
        private const string POLICY_FAILURE_NO_ASSOCIATED_CODE_REVIEW_WORKITEM = "There were no associated Code Review Work Items for this check in.";
        private const string POLICY_FAILURE_ASSOCIATED_BUGS_NOT_CLOSED = "Associated bugs in Code Review work items were not closed.";
        private const string POLICY_FAILURE_ASSOCIATED_CODE_REVIEW_WORKITEMS_NOT_APPROVED = "One or more associated Code Review work items was marked as Rejected.";
        private const string POLICY_FAILURE_WORKITEM_ALREADY_CLOSED = "Associated code review work item is already 'Closed'. Associated code review work items should be Resolved/Approved.";
        private const string CODE_REVIEW_WORKITEM_TYPENAME = "Code Review";
        private const string SELECT_ASSOCIATED_BUGS_QUERY = "SELECT [System.Id] FROM WorkItems WHERE [System.Id] = '{0}' AND [System.WorkItemType] = 'Bug'";
        private const string STATE_CLOSED = "Closed";
        private const string STATE_RESOLVED = "Resolved";
        private const string RESOLUTION_APPROVED = "Approved";
        private const string WORKITEM_RESOLUTION_FIELDNAME = "Resolution";

        #endregion Constants

        #region Overrides

        public override string Description
        {
            get { return POLICY_DESCRIPTION; }
        }

        public override string Type
        {
            get { return POLICY_TYPE; }
        }

        public override string TypeDescription
        {
            get { return POLICY_DESCRIPTION; }
        }

        public override bool CanEdit
        {
            get
            {
                return false;
            }
        }

        public new string InstallationInstructions
        {
            get
            {
                return INSTALLATION_INSTRUCTIONS;
            }
        }

        public override bool Edit(IPolicyEditArgs policyEditArgs)
        {
            return true;
        }

        public override PolicyFailure[] Evaluate()
        {
            List<PolicyFailure> failures = new List<PolicyFailure>();

            bool foundCodeReviewWorkItem = false;
            bool allBugsClosed = true;
            bool allCodeReviewsApproved = true;
            bool workitemAlreadyClosed = false;

            foreach (WorkItemCheckinInfo wiInfo in PendingCheckin.WorkItems.CheckedWorkItems)
            {
                if (wiInfo.WorkItem.Type.Name == CODE_REVIEW_WORKITEM_TYPENAME)
                {
                    // TODO: We need to add a post-build target that will set "Active" code review work
                    // items associated with a changeset to "Resolved(Approved)" upon successful build.
                    // Otherwise, we end up with a lot of orphaned code review work items.
                    foundCodeReviewWorkItem = true;
                    if (wiInfo.WorkItem.State == STATE_CLOSED)
                    {
                        workitemAlreadyClosed = true;
                        break;
                    }
                    else
                    {
                        if (wiInfo.WorkItem.State == STATE_RESOLVED)
                        {
                            if ((string)wiInfo.WorkItem.Fields[WORKITEM_RESOLUTION_FIELDNAME].Value == RESOLUTION_APPROVED)
                            {
                                // Check associated bugs, if any.
                                foreach (Link link in wiInfo.WorkItem.Links)
                                {
                                    RelatedLink wiLink = link as RelatedLink;
                                    if (wiLink != null)
                                    {
                                        if (wiLink.RelatedWorkItemId > 0) /* work item link */
                                        {
                                            // Is the work item a 'bug'?
                                            WorkItemCollection workItems = wiInfo.WorkItem.Store.Query(
                                                String.Format(CultureInfo.InvariantCulture,
                                                              SELECT_ASSOCIATED_BUGS_QUERY,
                                                              wiLink.RelatedWorkItemId.ToString(CultureInfo.InvariantCulture)));
                                            if (workItems != null && workItems.Count == 1)
                                            {
                                                if (workItems[0].State != STATE_CLOSED)
                                                {
                                                    allBugsClosed = false;
                                                }
                                            }
                                        }
                                    }
                                }

                                if (wiInfo.WorkItem.State == STATE_RESOLVED)
                                {
                                    //try
                                    //{
                                    //    wiInfo.WorkItem.State = STATE_CLOSED;
                                    //    wiInfo.WorkItem.Save();
                                    //}
                                    //catch { /* This function sometimes gets called twice */}
                                }
                            }
                            else
                            {
                                allCodeReviewsApproved = false;
                            }
                        }
                    }
                }
            }

            if (!foundCodeReviewWorkItem) failures.Add(new PolicyFailure(POLICY_FAILURE_NO_ASSOCIATED_CODE_REVIEW_WORKITEM, this));
            if (workitemAlreadyClosed) failures.Add(new PolicyFailure(POLICY_FAILURE_WORKITEM_ALREADY_CLOSED, this));
            if (!allBugsClosed) failures.Add(new PolicyFailure(POLICY_FAILURE_ASSOCIATED_BUGS_NOT_CLOSED, this));
            if (!allCodeReviewsApproved) failures.Add(new PolicyFailure(POLICY_FAILURE_ASSOCIATED_CODE_REVIEW_WORKITEMS_NOT_APPROVED, this));

            return failures.ToArray();
        }

        #endregion Overrides

    }
}
